1 /**
2  * Unrolled Linked List.
3  * Copyright: © 2015 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.unrolledlist;
9 
10 private import core.lifetime : move;
11 private import containers.internal.node : shouldAddGCRange;
12 private import std.experimental.allocator.mallocator : Mallocator;
13 
14 version (X86_64)
15 	version (LDC)
16 		version = LDC_64;
17 
18 /**
19  * Unrolled Linked List.
20  *
21  * Nodes are (by default) sized to fit within a 64-byte cache line. The number
22  * of items stored per node can be read from the $(B nodeCapacity) field.
23  * See_also: $(LINK http://en.wikipedia.org/wiki/Unrolled_linked_list)
24  * Params:
25  *     T = the element type
26  *     Allocator = the allocator to use. Defaults to `Mallocator`.
27  *     supportGC = true to ensure that the GC scans the nodes of the unrolled
28  *         list, false if you are sure that no references to GC-managed memory
29  *         will be stored in this container.
30  *     cacheLineSize = Nodes will be sized to fit within this number of bytes.
31  */
32 struct UnrolledList(T, Allocator = Mallocator,
33 	bool supportGC = shouldAddGCRange!T, size_t cacheLineSize = 64)
34 {
35 	this(this) @disable;
36 
37 	private import std.experimental.allocator.common : stateSize;
38 
39 	static if (stateSize!Allocator != 0)
40 	{
41 		/// No default construction if an allocator must be provided.
42 		this() @disable;
43 
44 		/**
45 		 * Use the given `allocator` for allocations.
46 		 */
47 		this(Allocator allocator)
48 		in
49 		{
50 			assert(allocator !is null, "Allocator must not be null");
51 		}
52 		do
53 		{
54 			this.allocator = allocator;
55 		}
56 	}
57 
58 	~this() nothrow
59 	{
60 		scope (failure) assert(false, "UnrolledList destructor threw an exception");
61 		clear();
62 	}
63 
64 	/**
65 	 * Removes all items from the list
66 	 */
67 	void clear()
68 	{
69 		Node* previous;
70 		Node* current = _front;
71 		while (current !is null)
72 		{
73 			previous = current;
74 			current = current.next;
75 
76 			static if (useGC)
77 			{
78 				import core.memory: GC;
79 				GC.removeRange(previous);
80 			}
81 			allocator.dispose(previous);
82 		}
83 		_length = 0;
84 		_front = null;
85 		_back = null;
86 	}
87 
88 	/**
89 	 * Inserts the given item into the end of the list.
90 	 *
91 	 * Returns: a pointer to the inserted item.
92 	 */
93 	T* insertBack(T item)
94 	{
95 		ContainerStorageType!T* result;
96 		if (_back is null)
97 		{
98 			assert (_front is null, "front/back nullness mismatch");
99 			_back = allocateNode(move(mutable(item)));
100 			_front = _back;
101 			result = &_back.items[0];
102 		}
103 		else
104 		{
105 			size_t index = _back.nextAvailableIndex();
106 			if (index >= nodeCapacity)
107 			{
108 				Node* n = allocateNode(move(mutable(item)));
109 				n.prev = _back;
110 				_back.next = n;
111 				_back = n;
112 				index = 0;
113 				result = &n.items[0];
114 			}
115 			else
116 			{
117 				_back.items[index] = move(mutable(item));
118 				_back.markUsed(index);
119 				result = &_back.items[index];
120 			}
121 		}
122 		_length++;
123 		assert (_back.registry <= fullBitPattern, "Overflow");
124 		return cast(T*) result;
125 	}
126 
127 	/**
128 	 * Inserts the given range into the end of the list
129 	 */
130 	void insertBack(R)(auto ref R range)
131 	{
132 		foreach (ref r; range)
133 			insertBack(r);
134 	}
135 
136 	/// ditto
137 	T* opOpAssign(string op)(T item) if (op == "~")
138 	{
139 		return insertBack(item);
140 	}
141 
142 	/// ditto
143 	alias put = insertBack;
144 
145 	/// ditto
146 	alias insert = insertBack;
147 
148 	/**
149 	 * Inserts the given item in the frontmost available cell, which may put the
150 	 * item anywhere in the list as removal may leave gaps in list nodes. Use
151 	 * this only if the order of elements is not important.
152 	 *
153 	 * Returns: a pointer to the inserted item.
154 	 */
155 	T* insertAnywhere(T item) @trusted
156 	{
157 		Node* n = _front;
158 		while (n !is null)
159 		{
160 			size_t i = n.nextAvailableIndex();
161 			if (i >= nodeCapacity)
162 			{
163 				if (n.next is null)
164 				{
165 					assert (n is _back, "Wrong _back");
166 					break;
167 				}
168 				n = n.next;
169 				continue;
170 			}
171 			n.items[i] = move(mutable(item));
172 			n.markUsed(i);
173 			_length++;
174 			assert (n.registry <= fullBitPattern, "Overflow");
175 			return cast(T*) &n.items[i];
176 		}
177 		n = allocateNode(move(mutable(item)));
178 		_length++;
179 		auto retVal = cast(T*) &n.items[0];
180 		if (_front is null)
181 		{
182 			assert(_back is null, "front/back nullness mismatch");
183 			_front = n;
184 		}
185 		else
186 		{
187 			n.prev = _back;
188 			_back.next = n;
189 		}
190 		_back = n;
191 		assert (_back.registry <= fullBitPattern, "Overflow");
192 		return retVal;
193 	}
194 
195 	/// Returns: the length of the list
196 	size_t length() const nothrow pure @property @safe @nogc
197 	{
198 		return _length;
199 	}
200 
201 	/// Returns: true if the list is empty
202 	bool empty() const nothrow pure @property @safe @nogc
203 	{
204 		return _length == 0;
205 	}
206 
207 	/**
208 	 * Removes the first instance of the given item from the list.
209 	 *
210 	 * Returns: true if something was removed.
211 	 */
212 	bool remove(ref const T item)
213 	{
214 		if (_front is null)
215 			return false;
216 		bool retVal;
217 		loop: for (Node* n = _front; n !is null; n = n.next)
218 		{
219 			foreach (i; 0 .. nodeCapacity)
220 			{
221 				if (!n.isFree(i) && n.items[i] == item)
222 				{
223 					n.markUnused(i);
224 					--_length;
225 					retVal = true;
226 					if (n.registry == 0)
227 						deallocateNode(n);
228 					else if (shouldMerge(n, n.next))
229 						mergeNodes(n, n.next);
230 					else if (shouldMerge(n.prev, n))
231 						mergeNodes(n.prev, n);
232 					break loop;
233 				}
234 			}
235 		}
236 		return retVal;
237 	}
238 	bool remove(const T item) { return remove(item); }
239 
240 	/// Pops the front item off of the list
241 	void popFront()
242 	{
243 		moveFront();
244 		assert (_front is null || _front.registry != 0, "Node is non-null but empty");
245 	}
246 
247 	/// Pops the front item off of the list and returns it
248 	T moveFront()
249 	in
250 	{
251 		assert (!empty(), "Accessing .moveFront of empty UnrolledList");
252 		assert (_front.registry != 0, "Empty node");
253 	}
254 	do
255 	{
256 		version (LDC_64)
257 		{
258 			import ldc.intrinsics : llvm_cttz;
259 			size_t index = llvm_cttz(_front.registry, true);
260 		}
261 		else
262 		{
263 			import containers.internal.backwards : bsf;
264 			size_t index = bsf(_front.registry);
265 		}
266 		T r = move(_front.items[index]);
267 		_front.markUnused(index);
268 		_length--;
269 		if (_front.registry == 0)
270 		{
271 			auto f = _front;
272 			if (_front.next !is null)
273 				_front.next.prev = null;
274 			assert (_front.next !is _front, "Infinite loop");
275 			_front = _front.next;
276 			if (_front is null)
277 				_back = null;
278 			else
279 				assert (_front.registry <= fullBitPattern, "Overflow");
280 			deallocateNode(f);
281 			return r;
282 		}
283 		if (shouldMerge(_front, _front.next))
284 			mergeNodes(_front, _front.next);
285 		return r;
286 	}
287 
288 	debug (EMSI_CONTAINERS) invariant
289 	{
290 		import std.string: format;
291 		assert (_front is null || _front.registry != 0, format("%x, %b", _front, _front.registry));
292 		assert (_front !is null || _back is null, "_front/_back nullness mismatch");
293 		if (_front !is null)
294 		{
295 			const(Node)* c = _front;
296 			while (c.next !is null)
297 				c = c.next;
298 			assert(c is _back, "_back pointer is wrong");
299 		}
300 	}
301 
302 	/**
303 	 * Time complexity is O(1)
304 	 * Returns: the item at the front of the list
305 	 */
306 	ref inout(T) front() inout nothrow @property
307 	in
308 	{
309 		assert (!empty, "Accessing .front of empty UnrolledList");
310 		assert (_front.registry != 0, "Empty node");
311 	}
312 	do
313 	{
314 		version (LDC_64)
315 		{
316 			import ldc.intrinsics : llvm_cttz;
317 			immutable index = llvm_cttz(_front.registry, true);
318 		}
319 		else
320 		{
321 			import containers.internal.backwards : bsf;
322 			immutable index = bsf(_front.registry);
323 		}
324 		return *(cast(typeof(return)*) &_front.items[index]);
325 	}
326 
327 	/**
328 	 * Time complexity is O(nodeCapacity), where the nodeCapacity
329 	 * is the number of items in a single list node. It is a constant
330 	 * related to the cache line size.
331 	 * Returns: the item at the back of the list
332 	 */
333 	ref inout(T) back() inout nothrow @property
334 	in
335 	{
336 		assert (!empty, "Accessing .back of empty UnrolledList");
337 		assert (!_back.empty, "Empty node");
338 	}
339 	do
340 	{
341 		size_t i = nodeCapacity - 1;
342 		while (_back.isFree(i))
343 			i--;
344 		return *(cast(typeof(return)*) &_back.items[i]);
345 	}
346 
347 	/// Pops the back item off of the list.
348 	void popBack()
349 	{
350 		moveBack();
351 	}
352 
353 	/// Removes an item from the back of the list and returns it.
354 	T moveBack()
355 	in
356 	{
357 		assert (!empty, "Accessing .moveBack of empty UnrolledList");
358 		assert (!_back.empty, "Empty node");
359 	}
360 	do
361 	{
362 		size_t i = nodeCapacity - 1;
363 		while (_back.isFree(i))
364 		{
365 			if (i == 0)
366 				break;
367 			else
368 				i--;
369 		}
370 		assert (!_back.isFree(i), "Empty node");
371 		T item = move(_back.items[i]);
372 		_back.markUnused(i);
373 		_length--;
374 		if (_back.registry == 0)
375 		{
376 			deallocateNode(_back);
377 			return item;
378 		}
379 		else if (shouldMerge(_back.prev, _back))
380 			mergeNodes(_back.prev, _back);
381 		return item;
382 	}
383 
384 	/// Returns: a range over the list
385 	auto opSlice(this This)() const nothrow pure @nogc @trusted
386 	{
387 		return Range!(This)(_front);
388 	}
389 
390 	static struct Range(ThisT)
391 	{
392 		@disable this();
393 
394 		this(inout(Node)* current)
395 		{
396 			import std.format:format;
397 
398 			this.current = current;
399 			if (current !is null)
400 			{
401 				version(LDC_64)
402 				{
403 					import ldc.intrinsics : llvm_cttz;
404 					index = llvm_cttz(current.registry, true);
405 				}
406 				else
407 				{
408 					import containers.internal.backwards : bsf;
409 					index = bsf(current.registry);
410 				}
411 
412 				assert (index < nodeCapacity);
413 			}
414 			else
415 				current = null;
416 		}
417 
418 		ref ET front() const nothrow @property @trusted @nogc
419 		{
420 			return *(cast(ET*) &current.items[index]);
421 			//return cast(T) current.items[index];
422 		}
423 
424 		void popFront() nothrow pure @safe @nogc
425 		{
426 			index++;
427 			while (true)
428 			{
429 
430 				if (index >= nodeCapacity)
431 				{
432 					current = current.next;
433 					if (current is null)
434 						return;
435 					index = 0;
436 				}
437 				else
438 				{
439 					if (current.isFree(index))
440 						index++;
441 					else
442 						return;
443 				}
444 			}
445 		}
446 
447 		bool empty() const nothrow pure @property @safe @nogc
448 		{
449 			return current is null;
450 		}
451 
452 		Range save() const nothrow pure @property @safe @nogc
453 		{
454 			return this;
455 		}
456 
457 	private:
458 
459 		alias ET = ContainerElementType!(ThisT, T);
460 		const(Node)* current;
461 		size_t index;
462 	}
463 
464 private:
465 
466 	import std.experimental.allocator: make, dispose;
467 	import containers.internal.node : FatNodeInfo, shouldAddGCRange,
468 		fullBits, shouldNullSlot;
469 	import containers.internal.storage_type : ContainerStorageType;
470 	import containers.internal.element_type : ContainerElementType;
471 	import containers.internal.mixins : AllocatorState;
472 
473 	alias N = FatNodeInfo!(T.sizeof, 2, cacheLineSize);
474 	enum size_t nodeCapacity = N[0];
475 	alias BookkeepingType = N[1];
476 	enum fullBitPattern = fullBits!(BookkeepingType, nodeCapacity);
477 	enum bool useGC = supportGC && shouldAddGCRange!T;
478 
479 	static ref ContainerStorageType!T mutable(ref T value) { return *cast(ContainerStorageType!T*)&value; }
480 
481 	Node* _back;
482 	Node* _front;
483 	size_t _length;
484 	mixin AllocatorState!Allocator;
485 
486 	Node* allocateNode(T item)
487 	{
488 		Node* n = make!Node(allocator);
489 		static if (useGC)
490 		{
491 			import core.memory: GC;
492 			GC.addRange(n, Node.sizeof);
493 		}
494 		n.items[0] = move(mutable(item));
495 		n.markUsed(0);
496 		return n;
497 	}
498 
499 	void deallocateNode(Node* n)
500 	{
501 		if (n.prev !is null)
502 			n.prev.next = n.next;
503 		if (n.next !is null)
504 			n.next.prev = n.prev;
505 		if (_front is n)
506 			_front = n.next;
507 		if (_back is n)
508 			_back = n.prev;
509 
510 		static if (useGC)
511 		{
512 			import core.memory: GC;
513 			GC.removeRange(n);
514 		}
515 		allocator.dispose(n);
516 	}
517 
518 	static bool shouldMerge(const Node* first, const Node* second)
519 	{
520 		if (first is null || second is null)
521 			return false;
522 		version (LDC_64)
523 		{
524 			import ldc.intrinsics : llvm_ctpop;
525 
526 			immutable f = llvm_ctpop(first.registry);
527 			immutable s = llvm_ctpop(second.registry);
528 		}
529 		else
530 		{
531 			import containers.internal.backwards : popcnt;
532 
533 			immutable f = popcnt(first.registry);
534 			immutable s = popcnt(second.registry);
535 		}
536 		return f + s <= nodeCapacity;
537 	}
538 
539 	void mergeNodes(Node* first, Node* second)
540 	in
541 	{
542 		assert (first !is null, "Invalid merge");
543 		assert (second !is null, "Invalid merge");
544 		assert (second is first.next, "Invalid merge");
545 	}
546 	do
547 	{
548 		size_t i;
549 		ContainerStorageType!T[nodeCapacity] temp;
550 		foreach (j; 0 .. nodeCapacity)
551 			if (!first.isFree(j))
552 				temp[i++] = move(first.items[j]);
553 		foreach (j; 0 .. nodeCapacity)
554 			if (!second.isFree(j))
555 				temp[i++] = move(second.items[j]);
556 		foreach (j; 0 .. i)
557 			first.items[j] = move(temp[j]);
558 		first.registry = 0;
559 		foreach (k; 0 .. i)
560 			first.markUsed(k);
561 		assert (first.registry <= fullBitPattern, "Overflow");
562 		deallocateNode(second);
563 	}
564 
565 	static struct Node
566 	{
567 		size_t nextAvailableIndex() const nothrow pure @safe @nogc
568 		{
569 			static if (BookkeepingType.sizeof < uint.sizeof)
570 				immutable uint notReg = ~(cast(uint) registry);
571 			else
572 				immutable auto notReg = ~registry;
573 			version (LDC_64)
574 			{
575 				import ldc.intrinsics : llvm_cttz;
576 				return llvm_cttz(notReg, true);
577 			}
578 			else
579 			{
580 				import containers.internal.backwards : bsf;
581 				return bsf(notReg);
582 			}
583 		}
584 
585 		void markUsed(size_t index) nothrow pure @safe @nogc
586 		{
587 			registry |= (BookkeepingType(1) << index);
588 		}
589 
590 		void markUnused(size_t index) nothrow pure @safe @nogc
591 		{
592 			registry &= ~(BookkeepingType(1) << index);
593 			static if (shouldNullSlot!T)
594 				items[index] = null;
595 		}
596 
597 		bool empty() const nothrow pure @safe @nogc
598 		{
599 			return registry == 0;
600 		}
601 
602 		bool isFree(size_t index) const nothrow pure @safe @nogc
603 		{
604 			return (registry & (BookkeepingType(1) << index)) == 0;
605 		}
606 
607 		debug(EMSI_CONTAINERS) invariant()
608 		{
609 			import std.string : format;
610 			assert (registry <= fullBitPattern, format("%016b %016b", registry, fullBitPattern));
611 			assert (prev !is &this, "Infinite loop");
612 			assert (next !is &this, "Infinite loop");
613 		}
614 
615 		BookkeepingType registry;
616 		ContainerStorageType!T[nodeCapacity] items;
617 		Node* prev;
618 		Node* next;
619 	}
620 }
621 
622 version(emsi_containers_unittest) unittest
623 {
624 	import std.algorithm : equal;
625 	import std.range : iota;
626 	import std.string : format;
627 	UnrolledList!ubyte l;
628 	static assert (l.Node.sizeof <= 64);
629 	assert (l.empty);
630 	l.insert(0);
631 	assert (l.length == 1);
632 	assert (!l.empty);
633 	foreach (i; 1 .. 100)
634 		l.insert(cast(ubyte) i);
635 	assert (l.length == 100);
636 	assert (equal(l[], iota(100)));
637 	foreach (i; 0 .. 100)
638 		assert (l.remove(cast(ubyte) i), format("%d", i));
639 	assert (l.length == 0, format("%d", l.length));
640 	assert (l.empty);
641 
642 	assert(*l.insert(1) == 1);
643 	assert(*l.insert(2) == 2);
644 	assert (l.remove(1));
645 	assert (!l.remove(1));
646 	assert (!l.empty);
647 
648 	UnrolledList!ubyte l2;
649 	l2.insert(1);
650 	l2.insert(2);
651 	l2.insert(3);
652 	assert (l2.front == 1);
653 	l2.popFront();
654 	assert (l2.front == 2);
655 	assert (equal(l2[], [2, 3]));
656 	l2.popFront();
657 	assert (equal(l2[], [3]));
658 	l2.popFront();
659 	assert (l2.empty, format("%d", l2.front));
660 	assert (equal(l2[], cast(int[]) []));
661 	UnrolledList!int l3;
662 	foreach (i; 0 .. 200)
663 		l3.insert(i);
664 	foreach (i; 0 .. 200)
665 	{
666 		auto x = l3.moveFront();
667 		assert (x == i, format("%d %d", i, x));
668 	}
669 	assert (l3.empty);
670 	foreach (i; 0 .. 200)
671 		l3.insert(i);
672 	assert (l3.length == 200);
673 	foreach (i; 0 .. 200)
674 	{
675 		assert (l3.length == 200 - i);
676 		auto x = l3.moveBack();
677 		assert (x == 200 - i - 1, format("%d %d", 200 - 1 - 1, x));
678 	}
679 	assert (l3.empty);
680 }
681 
682 version(emsi_containers_unittest) unittest
683 {
684 	struct A { int a; int b; }
685 	UnrolledList!(const(A)) objs;
686 	objs.insert(A(10, 11));
687 	static assert (is (typeof(objs.front) == const));
688 	static assert (is (typeof(objs[].front) == const));
689 }
690 
691 version(emsi_containers_unittest) unittest
692 {
693 	static class A
694 	{
695 		int a;
696 		int b;
697 
698 		this(int a, int b)
699 		{
700 			this.a = a;
701 			this.b = b;
702 		}
703 	}
704 
705 	UnrolledList!(A) objs;
706 	objs.insert(new A(10, 11));
707 }
708 
709 // Issue #52
710 version(emsi_containers_unittest) unittest
711 {
712 	UnrolledList!int list;
713 	list.insert(0);
714 	list.insert(0);
715 	list.insert(0);
716 	list.insert(0);
717 	list.insert(0);
718 
719 	foreach (ref it; list[])
720 		it = 1;
721 
722 	foreach (it; list[])
723 		assert(it == 1);
724 }
725 
726 // Issue #53
727 version(emsi_containers_unittest) unittest
728 {
729 	UnrolledList!int ints;
730 	ints.insertBack(0);
731 	ints.insertBack(0);
732 
733 	ints.front = 1;
734 	ints.back = 11;
735 
736 	assert(ints.front == 1);
737 	assert(ints.back == 11);
738 }
739 
740 // Issue #168
741 version(emsi_containers_unittest) unittest
742 {
743 	import std.typecons : RefCounted;
744 	alias E = RefCounted!int;
745 
746 	E e = E(12);
747 	UnrolledList!E ints;
748 	ints.insertBack(e);
749 	ints.clear();
750 	// crucial: no assert failure
751 	assert (e == 12);
752 }
753 
754 // Issue #170
755 version(emsi_containers_unittest) unittest
756 {
757 	static struct S { @disable this(this); }
758 	UnrolledList!S list;
759 
760 	list.insert(S());
761 	list.remove(S());
762 
763 	list.insert(S());
764 	S s;
765 	list.remove(s);
766 }